/*  ----------------------------------------------------------------------------
 *  Arello Mobile
 *  Fast 3d Engine
 *  ----------------------------------------------------------------------------
 * Except where otherwise noted, this work is licensed under
 * http://creativecommons.org/licenses/by/3.0
 */

#include "VertexBuffer.h"
#include "Hardware.h"
#include <logs/Log.h>
#include <Gluck.h>
#include "Creator.h"
#include <math/utl.h>

#include "IndexBuffer.h"

#define BUFFER_OFFSET(i) ((char *)NULL + (i))

namespace liba
{
namespace lib3d
{
namespace hard
{
namespace macx
{

VertexBuffer::VertexBuffer(Hardware * hardware, bool dynamic )
:	valid_flag( false ),
	vertex_buffer( 0 ),
	vertices_count( 0 ),
	stride( 0 ),
	dynamic( dynamic ),
	normals_stride( 0 ),
	colors_stride( 0 ),
	uvs_stride( 0 ),
	primitives_type( TRIANGLESLIST )
{
	glGenBuffers( 1, &vertex_buffer );
	if ( !vertex_buffer )
		THROW(Gluck(__FUNCTION__, "create_internal_vertex_buffer() failed"));
}

VertexBuffer::~VertexBuffer()
{
	VertexBuffer::reset_cached_buffer();
	valid_flag = false;

	glBindBuffer( GL_ARRAY_BUFFER, 0 );

	if( vertex_buffer )
		glDeleteBuffers( 1, &vertex_buffer );
	vertex_buffer = 0;
}

bool VertexBuffer::is_valid()const
{
	return valid_flag;
}

bool VertexBuffer::update(
		const SimpleArray<float> & positions,
		const SimpleArray<float> & normals,
		const SimpleArray<float> & u_tangents,
		const SimpleArray<float> & v_tangents,
		const SimpleArray<float> & uv0,
		const SimpleArray<float> & uv1,
		const SimpleArray<float> & weights0,
		const SimpleArray<float> & weights1,
		const SimpleArray<float> & weights2,
		const SimpleArray<int> & indices0,
		const SimpleArray<int> & indices1,
		const SimpleArray<int> & indices2,
		const SimpleArray<int> & indices3
		)
{
	int vertices_count = positions.size()/3;
	if( positions.size() != vertices_count*3 )
		return false;
	stride = 3 * sizeof(float);
	stride += 3 * sizeof(float);
	stride += 2 * sizeof(float);
	
	normals_stride = 3*sizeof(GLfloat);
	uvs_stride = 6*sizeof(GLfloat);
	colors_stride = 0;

	if( vertices_count == 0 )
	{
		this->vertices_count = vertices_count;
		valid_flag = true;
		return true;
	}
	
	VertexBuffer::reset_cached_buffer();
	glBindBuffer( GL_ARRAY_BUFFER, vertex_buffer );
	if( !valid_flag || this->vertices_count != vertices_count )
	{
		this->vertices_count = vertices_count;
	}
	
	SimpleArray<float> vertices_data(vertices_count*8);
	SimpleArray<float>::iterator data = vertices_data.begin();

	SimpleArray<float>::const_iterator posit = positions.begin();
	SimpleArray<float>::const_iterator norit = normals.begin();
	SimpleArray<float>::const_iterator uv0it = uv0.begin();
	for( int i = 0; i < vertices_count; ++i )
	{
		*data++ = *posit++;
		*data++ = *posit++;
		*data++ = *posit++;

		if( normals.size() != vertices_count*3 )
		{
			*data++ = 0;
			*data++ = 0;
			*data++ = 1;
		}
		else
		{
			*data++ = *norit++;
			*data++ = *norit++;
			*data++ = *norit++;
		}

		if( uv0.size() != vertices_count*2 )
		{
			*data++ = 0;
			*data++ = 0;
		}
		else
		{
			*data++ = *uv0it++;
			*data++ = *uv0it++;
		}

	}
	
	glBufferData( GL_ARRAY_BUFFER, stride * vertices_count, vertices_data.begin(), dynamic ? GL_DYNAMIC_DRAW : GL_STATIC_DRAW ); 

	valid_flag = true;
	return true;
}
	
#define fast_clamp(value, min_border, max_border) (value < min_border ? min_border : (value > max_border ? max_border : value))

bool VertexBuffer::update(
					const SimpleArray<float> & positions,
					const SimpleArray<float> & normals,
					const SimpleArray<float> & colors,
					const SimpleArray<float> & uv0,
					const SimpleArray<float> & uv1
					)
{
	int vertices_count = positions.size()/3;
	if( positions.size() != vertices_count*3 )
		return false;
	stride = 3 * sizeof(float);

	normals_stride = 0;
	uvs_stride = 0;
	colors_stride = 0;

	if( normals.size() )
	{
		normals_stride = stride;
		stride += 3 * sizeof(float);
	}
	if( colors.size() )
	{
		colors_stride = stride;
		stride += 4 * sizeof(unsigned char);
	}
	uvs_stride = stride;
	stride += 2 * sizeof(float);

	if( vertices_count == 0 )
	{
		this->vertices_count = vertices_count;
		valid_flag = true;
		return true;
	}

	VertexBuffer::reset_cached_buffer();
	glBindBuffer( GL_ARRAY_BUFFER, vertex_buffer );
	if( !valid_flag || this->vertices_count != vertices_count )
	{
		this->vertices_count = vertices_count;
	}

	SimpleArray<float> vertices_data(positions.size() + normals.size() + colors.size() + vertices_count*2);
	SimpleArray<float>::iterator data = vertices_data.begin();

	SimpleArray<float>::const_iterator posit = positions.begin();
	SimpleArray<float>::const_iterator norit = normals.begin();
	SimpleArray<float>::const_iterator colit = colors.begin();
	SimpleArray<float>::const_iterator uv0it = uv0.begin();
	for( int i = 0; i < vertices_count; ++i )
	{
		*data++ = *posit++;
		*data++ = *posit++;
		*data++ = *posit++;

		if( normals.size() != vertices_count*3 )
		{
		}
		else
		{
			*data++ = *norit++;
			*data++ = *norit++;
			*data++ = *norit++;
		}

		if( colors.size() != vertices_count*4 )
		{
		}
		else
		{
			unsigned char * char_data = (unsigned char *)data;
			//*char_data++ = fast_clamp( (*(colit))*255, 0, 255 );
			//*char_data++ = fast_clamp( (*(colit+1))*255, 0, 255 );
			//*char_data++ = fast_clamp( (*(colit+2))*255, 0, 255 );
			//*char_data++ = fast_clamp( (*(colit+3))*255, 0, 255 );
			*char_data++ = (*(colit))*255;
			*char_data++ = (*(colit+1))*255;
			*char_data++ = (*(colit+2))*255;
			*char_data++ = (*(colit+3))*255;
			colit += 4;
			*data++;
		}

		if( uv0.size() != vertices_count*2 )
		{
			*data++ = 0;
			*data++ = 0;
		}
		else
		{
			*data++ = *uv0it++;
			*data++ = *uv0it++;
		}

	}
	
	glBufferData( GL_ARRAY_BUFFER, stride * vertices_count, vertices_data.begin(), dynamic ? GL_DYNAMIC_DRAW : GL_STATIC_DRAW ); 

	valid_flag = true;
	return true;
}

void VertexBuffer::set_primitives_type(PrimitivesType primitiveType)
{
	primitives_type = primitiveType;
}

GLenum VertexBuffer::get_internal_primitives_type()
{
	switch(primitives_type)
	{
	case LINESLIST:
		return GL_LINES;
		
	case LINESSTRIP:
		return GL_LINE_STRIP;
	
	case TRIANGLESLIST:
		return GL_TRIANGLES;
	
	case TRIANGLESSTRIP:
		return GL_TRIANGLE_STRIP;
	}
	
	return GL_TRIANGLES;
}
	
int VertexBuffer::get_primitive_size()
{
	switch(primitives_type)
	{
		case LINESLIST:
			return 2;
				
		case LINESSTRIP:
			return 2;
				
		case TRIANGLESLIST:
			return 3;
			
		case TRIANGLESSTRIP:
			return 3;
	}
		
	return 3;
}
	
//stats
//static std::map<VertexBuffer *, int> used_buffers_map;
//static int buffer_switches = 0;
//static int avoided_switches = 0;
//static int possible_switches_to_avoid = 0;
	
//static int triangles_rendered = 0;
	
static VertexBuffer * last_buffer = 0;

bool VertexBuffer::render( int vertex_pos, int primitive_count )
{
//	triangles_rendered += primitive_count;
	
	if(last_buffer == this)
	{
//		avoided_switches++;
		glDrawArrays( get_internal_primitives_type(), vertex_pos, primitive_count * get_primitive_size() );
		return true;
	}
	
	if( vertices_count == 0 /*|| !hardware->is_operational()*/ )
		return true;

	if( vertex_pos + get_primitive_size() * primitive_count > vertices_count )
	{
		logs::log() << logs::error << "Vertex buffer overflow - " << primitive_count << " primitives at position " << vertex_pos << " while the total number of vertices is " << vertices_count << "\n";
		return false;
	}
	
//	if(used_buffers_map[this] == 1)
//	{
//		possible_switches_to_avoid++;
//	}
//	used_buffers_map[this] = 1;
//	buffer_switches++;

	glBindBuffer( GL_ARRAY_BUFFER, vertex_buffer );
	glVertexPointer( 3, GL_FLOAT, stride, BUFFER_OFFSET(0) );
	if( normals_stride )
	{
		glNormalPointer( GL_FLOAT, stride, BUFFER_OFFSET( normals_stride ) );
		glEnableClientState( GL_NORMAL_ARRAY );
		glEnable( GL_LIGHTING );
	}
	else
	{
		glDisableClientState( GL_NORMAL_ARRAY );
		glEnable( GL_LIGHTING );
	}

	if( colors_stride )
	{
		glColorPointer( 4, GL_UNSIGNED_BYTE, stride, BUFFER_OFFSET( colors_stride ) );
		glEnableClientState( GL_COLOR_ARRAY );
		glDisable( GL_LIGHTING );
	}
	else
	{
		glDisableClientState( GL_COLOR_ARRAY );
	}

	glTexCoordPointer( 2, GL_FLOAT, stride, BUFFER_OFFSET( uvs_stride ) );
	
	glDrawArrays( get_internal_primitives_type(), vertex_pos, primitive_count * get_primitive_size() );
	
	last_buffer = this;
	return true;
}

bool VertexBuffer::render( hard::IndexBuffer * ib, int index_pos, int primitive_count, int vertex_pos, int vertex_count )
{
//	triangles_rendered += primitive_count;
	
	if(last_buffer == this)
	{
//		avoided_switches++;
		glDrawElements(GL_TRIANGLES, primitive_count * get_primitive_size(), GL_UNSIGNED_SHORT, BUFFER_OFFSET(index_pos * 2));
		return true;
	}
	
	if( vertices_count == 0 /*|| !hardware->is_operational()*/ )
		return true;

//	if(used_buffers_map[this] == 1)
//	{
//		possible_switches_to_avoid++;
//	}
//	used_buffers_map[this] = 1;
//	buffer_switches++;
	
	glBindBuffer( GL_ARRAY_BUFFER, vertex_buffer );
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ((macx::IndexBuffer *)ib)->get_buffer());

	glVertexPointer( get_primitive_size(), GL_FLOAT, stride, BUFFER_OFFSET(0) );
	if( normals_stride )
	{
		glNormalPointer( GL_FLOAT, stride, BUFFER_OFFSET( normals_stride ) );
		glEnableClientState( GL_NORMAL_ARRAY );
		glEnable( GL_LIGHTING );
	}
	else
	{
		glDisableClientState( GL_NORMAL_ARRAY );
		glEnable( GL_LIGHTING );
	}
	
	if( colors_stride )
	{
		glColorPointer( 4, GL_UNSIGNED_BYTE, stride, BUFFER_OFFSET( colors_stride ) );
		glEnableClientState( GL_COLOR_ARRAY );
		glDisable( GL_LIGHTING );
	}
	else
		glDisableClientState( GL_COLOR_ARRAY );
	
	glTexCoordPointer( 2, GL_FLOAT, stride, BUFFER_OFFSET( uvs_stride ) );
	
	glDrawElements(GL_TRIANGLES, primitive_count * get_primitive_size(), GL_UNSIGNED_SHORT, BUFFER_OFFSET(index_pos * 2));
	
	last_buffer = this;
	return true;
}

bool VertexBuffer::render( hard::VertexBuffer * vb2, int vertex_pos, int primitive_count )
{
	return true;
}

bool VertexBuffer::render( hard::VertexBuffer * vb2, hard::IndexBuffer * ib, int index_pos, int primitive_count, int vertex_pos, int vertex_count )
{
	return true;
}
	
void VertexBuffer::reset_cached_buffer()
{
	last_buffer = 0;
}
	
void VertexBuffer::report_statistics()
{
//	logs::log() << "triangles rendered: " << triangles_rendered << "\n";
//	triangles_rendered = 0;
	
/*	logs::log() << "vertex buffer: switches: " << buffer_switches << " avoided: " << avoided_switches << " possible to avoid: " << possible_switches_to_avoid << "\n";
	
	used_buffers_map.clear();
	buffer_switches = 0;
	avoided_switches = 0;
	possible_switches_to_avoid = 0;
 */
}

} // namespace macx
} // namespace hard
} // namespace lib3d
} // namespace liba

